#include "cmd_options.h"
#include <iostream>
#include <sstream>

BEG_DEF_NAMESPACE_SMTPUTILS
CmdOptions * CmdOptions::GetInstance(){
	static CmdOptions gInstance;
	return &gInstance;
}

CmdOptions::CmdOptions(){
}

void CmdOptions::InsertOptBase(	OPT_STRUCT& opt,
				char shortChar, 
				const std::string& longStr, 
				const std::string& description, 
				bool hasArg, 
				bool hasDefault){
	opt.short_opt_char = shortChar;
	opt.long_opt_string = longStr;
	opt.instruction = description;
	opt.has_arg = hasArg;
	opt.result_value.has_default = hasDefault;
	return ;
}
void CmdOptions::InsertOptBOOL(	uint32_t id,
				char shortChar, 
				const std::string& longStr, 
				const std::string& description, 
				bool hasArg, 
				bool hasDefault, 
				bool defValue){
	OPT_STRUCT opt;
	opt.opt_type = BOOL;
	InsertOptBase( opt, shortChar, longStr, description, hasArg, hasDefault );
	if( hasDefault ){
		opt.result_value.content.content_bool = defValue;
	}
	mOptsBlob.insert( std::make_pair(id, opt) );	
	return;
}
void CmdOptions::InsertOptINT(uint32_t id,
				char shortChar, 
				const std::string& longStr, 
				const std::string& description, 
				bool hasArg, 
				bool hasDefault, 
				int defValue){
	OPT_STRUCT opt;
	opt.opt_type = INT;
	InsertOptBase( opt, shortChar, longStr, description, hasArg, hasDefault );
	if( hasDefault ){
		opt.result_value.content.content_int = defValue;
	}
	mOptsBlob.insert( std::make_pair(id, opt) );	
	return;
}
void CmdOptions::InsertOptFLOAT(uint32_t id,
				char shortChar, 
				const std::string& longStr, 
				const std::string& description, 
				bool hasArg, 
				bool hasDefault, 
				float defValue){
	OPT_STRUCT opt;
	opt.opt_type = FLOAT;
	InsertOptBase( opt, shortChar, longStr, description, hasArg, hasDefault );
	if( hasDefault ){
		opt.result_value.content.content_float = defValue;
	}
	mOptsBlob.insert( std::make_pair(id, opt) );	
	return;
}
void CmdOptions::InsertOptSTRING(uint32_t id,
				char shortChar, 
				const std::string& longStr, 
				const std::string& description, 
				bool hasArg, 
				bool hasDefault, 
				const std::string& defValue){
	OPT_STRUCT opt;
	opt.opt_type = STRING;
	InsertOptBase( opt, shortChar, longStr, description, hasArg, hasDefault );
	if( hasDefault ){
		opt.result_value.content.content_string = defValue;
	}
	mOptsBlob.insert( std::make_pair(id, opt) );	
	return;
}

int32_t CmdOptions::BuildDescriptionString(){
	
	OptsContainer::iterator iter; 
	for( iter = mOptsBlob.begin();
	     iter !=mOptsBlob.end();
	     iter ++ ){
		const OPT_STRUCT& opt = iter->second; 
		if( '\0' == opt.short_opt_char ){
			continue;
		}
		mDescriptionString += opt.short_opt_char;
		if( opt.has_arg ){
			mDescriptionString += ':';
		}
	}
	return 0;
}

int32_t CmdOptions::BuildDescriptionOption(){
	OptsContainer::iterator iter; 
	for( iter = mOptsBlob.begin();
	     iter !=mOptsBlob.end();
	     iter ++ ){

		const OPT_STRUCT& rOpt = iter->second; 
		if( rOpt.long_opt_string.empty() ){
			continue;
		}

		struct option tmp_option;
		memset( &tmp_option, 0 , sizeof( struct option) );
		tmp_option.name = rOpt.long_opt_string.c_str();
		if( rOpt.has_arg ){
			tmp_option.has_arg	= rOpt.has_arg;
			tmp_option.val	= rOpt.short_opt_char;
			tmp_option.flag	= NULL;
		}
		mOptionsVec.push_back( tmp_option );
	}
	return 0;
}

void CmdOptions::SetRetValue( OPT_STRUCT& rOpt, char * OptArg ){
	rOpt.result_value.has_default = true;
	if( !rOpt.has_arg ){
		return;
	}
	switch( rOpt.opt_type ){
	case BOOL:
		if( (0 == strcmp(OptArg, "true")) ||
		    (0 == strcmp(OptArg, "1")) ||
		    (0 == strcmp(OptArg, "yes")) 
		){
			rOpt.result_value.content.content_bool = true;
		}else{
			rOpt.result_value.content.content_bool = false;
		}
		break;
	case INT:
		rOpt.result_value.content.content_int = atoi( OptArg );
		break;
	case FLOAT:
		rOpt.result_value.content.content_float = atof( OptArg );
		break;
	case STRING:
	default:
		rOpt.result_value.content.content_string = OptArg;
		break;
	}
	return;
}
int32_t CmdOptions::HandleOpts( const std::string& versionInfo, int argc, char ** argv ){

	mVersionInfo = versionInfo;
	mProgramName = argv[0];
	struct option * long_options_p = NULL;
	int option_index = 0;
	int c=0;
	if( 0 != BuildDescriptionString() ){
		return -1;
	}
	if( 0 != BuildDescriptionOption() ){
		return -2;
	}

	if( !mOptionsVec.empty() ){
		uint32_t size = mOptionsVec.size();
		long_options_p = new option[size]();
		memset( long_options_p, 0, sizeof( struct option)*size );
		for(int i=0;i<size;i++){
			long_options_p[i] = mOptionsVec[i];
		}
	}

	do{	
		OptsContainer::iterator iter;
		c = getopt_long_only( argc, argv, mDescriptionString.c_str(), long_options_p, &option_index );
		switch( c ){
		case ':':
			std::cout << "missing parameter for option(s)"<<std::endl;
			return -3;
		case '?':
			std::cout << "Unknown option character(s)" <<std::endl;
			return -4;
		case -1:
			break;
		case '\0':
			for( iter = mOptsBlob.begin();
			     iter !=mOptsBlob.end();
			     iter++ ){
				OPT_STRUCT &rOpt = iter->second;
				if( strncmp( 	rOpt.long_opt_string.c_str(), 
						long_options_p[option_index].name, 
						rOpt.long_opt_string.size()) ){
					continue;
				}
				SetRetValue( rOpt, optarg );
			}
			break;
		default:
			for( iter = mOptsBlob.begin();
			     iter !=mOptsBlob.end();
			     iter++ ){
				OPT_STRUCT &rOpt = iter->second;
				if( c != rOpt.short_opt_char ){
					continue;
				}
				SetRetValue( rOpt, optarg );
			}
			break;
		}
	}while( c >= 0 );
	delete[] long_options_p;
	long_options_p = NULL;
	return 0;
}

bool CmdOptions::HasEnable( uint32_t id ){
	OptsContainer::iterator iter = mOptsBlob.find(id);
	bool ret = false;
	if( iter != mOptsBlob.end() ){
		if( iter->second.result_value.has_default ){
			ret = true;
		}
	}
	return ret;
}

bool CmdOptions::GetBool( uint32_t id, bool& retBool ){
	OptsContainer::iterator iter = mOptsBlob.find(id);
	bool ret = false;
	if( iter != mOptsBlob.end() ){
		OPT_STRUCT& opt = iter->second;	
		if( opt.has_arg &&opt.result_value.has_default ){
			retBool = opt.result_value.content.content_bool;
			ret = true;
		}
	}
	return ret;
}

bool CmdOptions::GetInt( uint32_t id, int& retInt){
	OptsContainer::iterator iter = mOptsBlob.find(id);
	bool ret = false;
	if( iter != mOptsBlob.end() ){
		OPT_STRUCT& opt = iter->second;	
		if( opt.has_arg &&opt.result_value.has_default ){
			retInt = opt.result_value.content.content_int;
			ret = true;
		}
	}
	return ret;
}

bool CmdOptions::GetFloat( uint32_t id, float& retFloat ){
	OptsContainer::iterator iter = mOptsBlob.find(id);
	bool ret = false;
	if( iter != mOptsBlob.end() ){
		OPT_STRUCT& opt = iter->second;	
		if( opt.has_arg &&opt.result_value.has_default ){
			retFloat = opt.result_value.content.content_float;
			ret = true;
		}
	}
	return ret;
}

bool CmdOptions::GetString( uint32_t id, std::string& retString ){
	OptsContainer::iterator iter = mOptsBlob.find(id);
	bool ret = false;
	if( iter != mOptsBlob.end() ){
		OPT_STRUCT& opt = iter->second;	
		if( opt.has_arg &&opt.result_value.has_default ){
			retString = opt.result_value.content.content_string;
			ret = true;
		}
	}
	return ret;
}
void CmdOptions::Usage() const{
	char * name = strdup( mProgramName.c_str() );
	if( NULL == name ){
		std::cerr<<"strdup returns a NULL pointer"<<std::endl;
		return ;
	}

	std::string output = basename(name);
	output += " - ";
	output += mVersionInfo;
	output += " - Build at:"__DATE__" "__TIME__ ;
	output +="\n";


	output +="Usage:\n";
	OptsContainer::const_iterator iter;
	for( iter = mOptsBlob.begin();
	     iter !=mOptsBlob.end();
	     iter ++ ){
		output += " ";
		OPT_STRUCT opt = iter->second;
		if( '\0' != opt.short_opt_char ){
			output += "-";
			output += opt.short_opt_char;
		}
		if( !opt.long_opt_string.empty() ){
			output += "|--";
			output += opt.long_opt_string;
		}
		
		if( opt.has_arg ){
			output += " <arguments>";
		}
		output += " : ";
		output += opt.instruction;
		output += "\n";
	}
	std::cout << output <<std::endl;
	return;
}

END_DEF_NAMESPACE_SMTPUTILS
